/*
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
#include <stdlib.h>
#include "apr_event.h"

static apr_status_t event_cb(void *baton, apr_pollfd_t * pfd) 
{
    apr_status_t rv;
    apr_event_loop_t *loop = (apr_event_loop_t*)baton;
    apr_event_io_t *io = (apr_event_io_t*)pfd->client_data;

    rv = apr_pollcb_remove(loop->pollcb, pfd);
    if (rv) {
        return rv;
    }

    (*io->func)(loop, io);

    return APR_SUCCESS;
}

apr_status_t apr_event_loop_create(apr_event_loop_t **loop, 
                                   apr_uint32_t flags,
                                   apr_pool_t *p)
{
    apr_status_t rv;
    apr_pollcb_t *pollcb;

    rv = apr_pollcb_create(&pollcb,
                           512,
                           p,
                           flags);

    if (rv == APR_SUCCESS) {
        *loop = apr_palloc(p, sizeof(**loop));
        (*loop)->pool = p;
        (*loop)->pollcb = pollcb;
        (*loop)->state = APR_EVENT_LOOP_INIT;
        APR_RING_INIT(&(*loop)->timer_ring, apr_event_timer_t, link);
        rv = apr_thread_mutex_create(&(*loop)->mtx, 0, p);
    }

    return rv;
}

void apr_event_io_initsock(apr_event_io_t **io, apr_event_io_cb_t func,
                           apr_socket_t *sock, apr_int32_t flags, void *baton, 
                           apr_pool_t *pool)
{
    apr_pollfd_t *pfd;

    *io = apr_palloc(pool, sizeof(**io));
    (*io)->pool = pool;
    (*io)->loop = NULL;
    (*io)->func = func;
    (*io)->baton = baton;

    pfd = apr_palloc(pool, sizeof(*pfd)); 
    pfd->p = pool;
    pfd->desc_type = APR_POLL_SOCKET;
    pfd->desc.s = sock;
    pfd->reqevents = flags;
    pfd->client_data = *io;

    (*io)->pfd = pfd;
}

apr_status_t apr_event_io_add(apr_event_loop_t *loop, apr_event_io_t *io)
{
    apr_status_t rv = APR_EGENERAL;

    io->loop = loop;
    if (io->pfd->desc_type == APR_POLL_SOCKET) {
        rv = apr_socket_opt_set(io->pfd->desc.s, APR_SO_NONBLOCK, 1);
    }
    else if (io->pfd->desc_type == APR_POLL_FILE) {
        /* TODO */
        abort();
    }
    if (rv) {
        return rv;
    }

    return apr_pollcb_add(loop->pollcb, io->pfd);
}

int apr_event_loop(apr_event_loop_t *loop)
{
    apr_status_t rv;
    apr_event_timer_t *et;
    loop->state = APR_EVENT_LOOP_RUNNING;

    while (loop->state == APR_EVENT_LOOP_RUNNING) {
        apr_time_t tnow = apr_time_now();
        apr_event_timer_t *head;
        apr_interval_time_t timeout = apr_time_from_msec(500);
        APR_RING_HEAD(temp_timer_ring_t, apr_event_timer_t) tmp_ring;

        apr_thread_mutex_lock(loop->mtx);
        head = APR_RING_FIRST(&loop->timer_ring);

        if (head != APR_RING_SENTINEL(&loop->timer_ring, apr_event_timer_t, link)) {
            if (tnow < head->expires) {
                timeout = (head->expires - tnow);
                if (timeout > apr_time_from_msec(500)) {
                    /* pqXXXXX: I'm 95% sure that the Linux Powertop guys will slap me for this. */
                    timeout = apr_time_from_msec(500);
                }
            }
            else {
                /* We have already expired timers in the queue. */
                timeout = 0;
            }
        }
        apr_thread_mutex_unlock(loop->mtx);

        /* Poll */
        rv = apr_pollcb_poll(loop->pollcb, timeout, event_cb, loop);
        if (rv) {
            if (!APR_STATUS_IS_EINTR(rv) && !APR_STATUS_IS_TIMEUP(rv)) {
                return rv;
            }
        }

        /* Run Timers */
        APR_RING_INIT(&tmp_ring, apr_event_timer_t, link);

        apr_thread_mutex_lock(loop->mtx);

        APR_RING_CHECK_CONSISTENCY(&loop->timer_ring, apr_event_timer_t, link);

        /* now iterate any timers */
        if (!APR_RING_EMPTY(&loop->timer_ring, apr_event_timer_t, link)) {
            for (et = APR_RING_FIRST(&loop->timer_ring);
                 et != APR_RING_SENTINEL(&loop->timer_ring,
                                         apr_event_timer_t, link);
                 et = APR_RING_NEXT(et, link)) {
                if (et->expires < tnow) {
                    apr_event_timer_t *next = APR_RING_PREV(et, link);
                    /* push this task */
                    APR_RING_REMOVE(et, link);
                    APR_RING_CHECK_CONSISTENCY(&loop->timer_ring,
                                               apr_event_timer_t, link);
                    APR_RING_INSERT_TAIL(&tmp_ring, et, apr_event_timer_t, link);
                    et = next;
                }
                else {
                    break;
                }
            }
        }

        APR_RING_CHECK_CONSISTENCY(&loop->timer_ring, apr_event_timer_t, link);

        apr_thread_mutex_unlock(loop->mtx);

        if (!APR_RING_EMPTY(&tmp_ring, apr_event_timer_t, link)) {
            for (et = APR_RING_FIRST(&tmp_ring);
                 et != APR_RING_SENTINEL(&tmp_ring,
                                         apr_event_timer_t, link);
                 et = APR_RING_NEXT(et, link)) {
                et->cb(et, et->baton);
            }
        }
    }
    
    return 0;
}

